package net.huazai.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import constant.CacheKey;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.huazai.entity.LoginUser;
import net.huazai.entity.UserLoginEntity;
import net.huazai.entity.UserRegisterEntity;
import net.huazai.enums.BizCodes;
import net.huazai.enums.SendCodes;
import net.huazai.interceptor.LoginInterceptor;
import net.huazai.mapper.UserMapper;
import net.huazai.model.UserDO;
import net.huazai.service.CaptchaService;
import net.huazai.service.UserService;
import net.huazai.utils.ApiResult;
import net.huazai.utils.CommonUtil;
import net.huazai.utils.JwtUtil;
import net.huazai.vo.UserVo;
import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 11987
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService{

    /**
     * 注入验证码服务
     */
    @Autowired
    private CaptchaService captchaService;

    /**
     * 注入用户 mapper
     */
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 用户注册核心逻辑
     * 1、检查邮箱验证码是否正确
     * 2、密码安全、高并发下账号唯一性检测
     * 3、入库
     */
    @Override
    public ApiResult register(UserRegisterEntity userRegisterEntity) {
        boolean checkCode = false;

        /*
          1、检查邮箱验证码是否正确
         */
        // 判断邮箱是否为空
        if(StringUtils.isNotBlank(userRegisterEntity.getMail())){
            // 如果不为空，则检查验证码是否正确
            checkCode = captchaService.checkCode(SendCodes.USER_REGISTER,
                    userRegisterEntity.getMail(),
                    userRegisterEntity.getCode());
        }
        // 如果验证码不正确，之间返回验证码错误
        if(!checkCode){
            // TODO 手机号验证码此处还未处理
            return ApiResult.doResult(BizCodes.USER_CODE_EMAIL_ERROR);
        }

        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userRegisterEntity, userDO);
        userDO.setCreateTime(new Date());
        userDO.setUpdateTime(new Date());

        /*
          1、密码安全，设置密码
         */
        // 生成盐 salt
        userDO.setSecret("$1$" + CommonUtil.getRandomSalt(8));
        // 生成密码  密码 + 盐 slat
        String pwd = Md5Crypt.md5Crypt(userRegisterEntity.getPassword().getBytes(), userDO.getSecret());
        userDO.setPwd(pwd);

        // 账号唯一性检测 TODO
        if(checkUnique(userDO.getPhone())){
            int row = userMapper.insert(userDO);
            log.info("用户注册模块-影响行数：{}, 注册成功:{}", row, userDO);

            // 新用户注册成功，需要初始化信息，发送福利等  TODO
            initTask(userDO);
            return ApiResult.doSuccess();
        } else {
            // 用户账号已存在，无法注册
            return ApiResult.doResult(BizCodes.USER_ACCOUNT_EXIST);
        }
    }

    /**
     * 检测用户账号唯一性
     * @param phone 手机号
     * @return true 表示账号不存在，可以注册
     */
    private boolean checkUnique(String phone) {
        QueryWrapper<UserDO> queryWrapper = new QueryWrapper<UserDO>()
                .eq("phone", phone);

        List<UserDO> list = userMapper.selectList(queryWrapper);
        log.info("用户注册模块-查询手机号结果：{}", list.toArray());
        return list.isEmpty();
    }

    /**
     * 用户注册成功，需要初始化信息，发送福利等
     * @param userDO 用户信息
     */
    private void initTask(UserDO userDO){
        // TODO
    }

    /**
     * 1、查找手机号是否已经存在，存在表示已注册，否则未注册
     * 2、匹配密码是否正确，正确则登录成功，否则提示账号/密码错误
     * 3、登录成功 token 解决方案 TODO
     */
    @Override
    public ApiResult login(UserLoginEntity userLoginEntity) {
        /*
          1、查找手机号是否存在
         */
        List<UserDO>  userList = userMapper.selectList(new QueryWrapper<UserDO>().eq("phone", userLoginEntity.getPhone()));
        if (userList != null && userList.size() == 1) {
            // 表示该手机号已经注册了
            UserDO userDO = userList.get(0);
            String pwd = Md5Crypt.md5Crypt(userLoginEntity.getPassword().getBytes(), userDO.getSecret());
            // 如果请求的密码加密后跟数据库的匹配
            if (pwd.equals(userDO.getPwd())) {
                // 登录成功，生成 Token
                LoginUser loginUser = new LoginUser();
                // 拷贝
                BeanUtils.copyProperties(userDO, loginUser);
                // 生成 JWT Token，过期时间
                Map<String,Object> tokenInfo = JwtUtil.generatorJwt(loginUser);
                // 设置 RefreshToken 到 Redis 中，过期时间为 30 天
                String refreshCacheKey = String.format(CacheKey.REFRESH_TOKEN_KEY
                        , tokenInfo.get("RefreshToken").toString());
                redisTemplate.opsForValue().set(refreshCacheKey, "1"
                        , JwtUtil.REFRESH_EXPIRE, TimeUnit.MILLISECONDS);
                log.info("用户登录模块-请求参数：{}，登录成功：{}，生成 token：{}" +
                        ", refreshCacheKey：{}", userLoginEntity
                        , userDO, tokenInfo, refreshCacheKey);
                return ApiResult.doSuccess(tokenInfo);
            } else {
                // 账号/密码错误
                return ApiResult.doResult(BizCodes.USER_ACCOUNT_PWD_ERROR);
            }
        } else {
            // 未注册
            return ApiResult.doResult(BizCodes.USER_ACCOUNT_UNREGISTER);
        }
    }

    @Override
    public ApiResult refreshToken(Map<String, Object> params) {
        // 1、先查找 redis 是否存在 refreshToken
        String refreshCacheKey = String.format(CacheKey.REFRESH_TOKEN_KEY
                , params.get("refresh_token").toString());
        String refreshCacheVal = redisTemplate.opsForValue().get(refreshCacheKey);
        log.info("用户刷新模块-参数：{}, 缓存验证码key：{}，缓存验证码val：{}", params, refreshCacheKey, refreshCacheVal);
        /*
          如果不为空，则判断是否匹配
         */
        if (StringUtils.isNotBlank(refreshCacheVal)) {
            // 2、如果存在，解密 accessToken
            Claims claims = JwtUtil.checkJwt(params.get("access_token").toString());
            if (claims == null) {
                // 无法解密提示未登录
                return ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN);
            }
            // 3、如果可以解密 accessToken， 则重新生成 accessToken 等信息返回
            long userId = Long.parseLong(claims.get("id").toString());
            List<UserDO>  userList = userMapper.selectList(new QueryWrapper<UserDO>().eq("id", userId));
            if (userList != null && userList.size() == 1) {
                UserDO userDO = userList.get(0);
                // 登录成功，生成 Token
                LoginUser loginUser = new LoginUser();
                // 拷贝
                BeanUtils.copyProperties(userDO, loginUser);
                // 生成 JWT Token，过期时间
                Map<String,Object> tokenInfo = JwtUtil.generatorJwt(loginUser);
                // 4、设置 RefreshToken 到 Redis 中，过期时间为 30 天
                String refreshNewCacheKey = String.format(CacheKey.REFRESH_TOKEN_KEY, tokenInfo.get("RefreshToken").toString());
                redisTemplate.opsForValue().set(refreshNewCacheKey, "1"
                        , JwtUtil.REFRESH_EXPIRE, TimeUnit.MILLISECONDS);
                log.info("用户刷新模块-请求参数：{}，登录成功：{}，生成 token：{}" +
                        ", refreshCacheKey：{}", params, userDO
                        , tokenInfo, refreshCacheKey);
                // TODO 可以删除旧的 refreshToken
                redisTemplate.delete(refreshCacheKey);
                return ApiResult.doSuccess(tokenInfo);
            } else {
                // 无法解密提示未登录
                return ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN);
            }
        } else {
            // refreshToken 不存在
            return ApiResult.doResult(BizCodes.USER_REFRESH_TOKEN_EMPTY);
        }
    }

    @Override
    public UserVo getUserDetail() {
        // 这里我们如何获取登录用户信息呢，前面我们通过 ThreadLocal 进行了用户信息的传递
        LoginUser loginUser = LoginInterceptor.LOGIN_USER_THREAD_LOCAL.get();
        UserDO userDO = userMapper.selectOne(new QueryWrapper<UserDO>().eq("id", loginUser.getId()));
        // 这里我们返回 userVo 的信息, 需要通过拷贝 userDO 到 userVo 中
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(userDO, userVo);
        return userVo;
    }
}
